<?php
	// 基本的面向对象编程
class Person {
	// var 开头的是类的属性
	var $name;
	var $age;
	// PHP 允许用可见性来定义属性(var 默认为 public)
	public $hobby;
	function say_name() {
		echo $this->name;
	}
	// PHP 允许用可见性来修饰函数 (默认为 public)
	public function say_age() {
		// 就像 CPP 里的 this 一样, 指向对象本身
		// 类内必须通过 this 来访问属性
		$this->say_name();
		echo $this->age;
	}
	// 类不能分散到多个文件并用 include 组合,
	// 但是方法中可以包含 include
	// 构造函数是一个 magic function (__打头)
	// 不能设成 private
	public function __construct($n, $a, $h) {
		$this->name = $n;
		$this->age = $a;
		$this->hobby = $h;
	}

}
// using new keyword 实例化对象
$a = new Person("helei", 32, "play");
// 多种实例化的方式
$name = "Person";
$b = new $name("he", 23, "play");	// 字符串
$b = new $a("haha", 1, "nono");	// 另一个对象

// 通过 -> 和属性名来设置属性
$a->say_name();
$a->say_age();

// 魔术方法 - PHP 内部规定的特殊方法, 能在某些事件发生时执行
// 以双下划线 __ 开头
// __destruct 析构函数
class Rectangle {
	public $height;
	public $width;
	public function area() {
		return $this->height * $this->width;
	}

	public function __construct() {
		$this->height = 0;
		$this->width = 0;
		echo "Object has been constructed. <br/>";
	}
	public function __destruct() {
		echo "Object has been destroyed. <br/>";
	}
	// __get 和 __set 在访问不可访问的变量时发生(私有 or 不存在)
	// 提供了这个函数就没有 fatal error 了
	public function __get($name) {
		echo "$name does not exist. <br />";
	}
	// 不提供这个函数则会创建一个新属性
	public function __set($name, $val) {
		echo "cannot set $name to $val <br />";
	}
	// __call 在调用一个不可调用的方法时被调用
	public function __call($func, $args) {
		echo "cannot call $func with arg:";
		foreach ($args as $a) {
			echo "$a ";
		}
		echo "<br />";
	}
	// __callStatic 在静态调用一个不可静态调用的方法时被调用
	// 这个函数必须是 static 的
	// 其它和 call 一样
	public static function __callStatic($func, $args) {
		echo "cannot statically call $func with arg:";
		foreach ($args as $a) {
			echo "$a ";
		}
		echo "<br />";
	}
	// __isset 在对一个不存在或不可访问的属性调用 isset 时被调用
	// 若不定义, 则系统默默 pass
	public function __isset($prop) {
		echo "$prop not exist ! <br />";
	}
	// __unset 在对一个不存在或不可访问的属性调用 unset 时被调用
	// 若不定义, 则系统默默 pass
	public function __unset($prop) {
		echo "cannot unnset $prop <br />";
	}

	private $wtf;
	private function say_wtf() {
		echo "what the fuck <br />";
	}
}

$rct = new Rectangle();
echo "do something <br/>";
$rct->wt2f = 5;
echo "$rct->wt2f";
$rct->say_wtf(1, 2, 3);
Rectangle::haha("w");
isset($rct->asdasd);
unset($rct->heyhe);
unset($rct);
echo "do other things <br/>";

// 类成员的可视性 参考 CPP
// public 可以在里面外面以及派生类中访问
// private 只能在里面访问
// protected 可以在里面和派生类的里面访问

// 静态属性和静态方法
// 它们在默认的情况下都是 public 的
class Dog {
	private static $number;
	public function __construct() {
		// 访问静态成员必须用 self
		// 或者 parent (基类)
		// 注意这个 $ 符号
		self::$number++;
	}
	// 静态方法没有 $this, 可以访问静态成员
	public static function dog_count() {
		echo "Now we have ", self::$number," dogs <br/>";
	}
}
$dog_team = array();
for ($i=0; $i < 5; $i++) { 
	$d = new Dog();
	array_push($dog_team, $d);
}
Dog::dog_count();

// 类的继承
// 普通类的继承
class Human {
	public $name;
	public function say() {
		echo "Im a human! $this->name <br />";
	}
}

class Man extends Human {
	// 其实是同一个变量
	public $name;
	public function say() {
		$this->name = "lalala";
		// 调用基类方法的方式
		parent::say();
		echo "Im a man man <br/>";
	}
}
$m = new Man();
$m->say();
// 抽象类由 abstract 关键字 + class 规定
// 不一定要有 abstract 方法
abstract class Animal {
	// abstract 方法肯定不能写成 private, 常识
	abstract protected function say();
}
class Pig extends Animal {
	// 子类中的抽象方法的实现, 可见性可以比抽象基类宽松
	public function say() {
		echo "Im a pig! <br />";
	}
}

$pg = new Pig();
$pg->say();

// 接口的所有签名都是 public 的并且不能在接口中实现
// 接口可以继承，多重继承接口
// 类可以 implements 多个接口但是接口间的前面不能重复
// 实现的函数必须是 public 的！
interface Html {
	public function set_name($n);
}

class Div implements Html {
	public $name;
	public function set_name($n) {
		$this->name = $n;
	}
}
$d = new Div();
$d->set_name("what!?");
echo "$d->name <br/>";

// interface 与抽象类的区别在于
// 抽象类本身还是属于一个类，具有类的特点以及限制

// 覆盖与重载
// 覆盖是子类重写了父类的方法

class Father {
	public function hobby() {
		return " eating ";
	}
	public function intro() {
		$h = $this->hobby();
		return "Hi I like $h <br />";
	}
}

// 默认具有类似 CPP 虚函数的性质
// 实际上应该是在这个类中按某种名字查找表来选择方法
class Son extends Father {
	public function hobby() {
		return " sleeping, dancing ";
	}
	// 利用魔术方法来实现函数的重载
	// 魔术方法的返回值直接返回给调用者
	public function __call($func, $arg) {
		if ($func == "foo") {
			return count($arg);
		}
	}
}

$s = new Son();
echo $s->intro();
echo $s->foo(5, 2, 3), "<br />";

// 对象的拷贝
// 默认对象的赋值运算其实是一种引用的传递
// 如果要按值拷贝，需要 clone 关键字
class Val {
	public $v;
	// 该魔术方法会在新生成的对象上被调用
	public function __clone() {
		$this->v = "cloned~~";
	}
}

$a = new Val();
$b = $a;
$c = clone $a;
$b->v = 5;
echo $a->v, "<br />";
echo $c->v, "<br />";

// 类方法的链式调用
// 需要最后返回一个 $this

// final 类不能被继承, final 方法不能被覆盖
final class TheEnd {}
//error: class Start extends TheEnd {}
class Start {
	public final function foo() {}
}
// error : 
// class Next extends Start {
// 	public function foo() {}
// }

// 对象的序列化和反序列化
class Book {
	public $title;
	public $author;
	public $year;
	public function __construct($t, $a, $y) {
		$this->title = $t;
		$this->author = $a;
		$this->year = $y;
	}
	public function __sleep() {
		$this->title .= "[damaged]";
		return array("title", "author", "year");
	}
	public function __wakeup() {
		$this->author = "[*******]";
	}
}

$b = new Book("Harry Potter", "He Lei", 1998);
$data = serialize($b);
$data = unserialize($data);
print_r($data);

?>